package com.leoandroid.tool.toolsbox.pedometer;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.widget.RemoteViews;

import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;

import com.leoandroid.tool.toolsbox.largeread.BuildConfig;
import com.leoandroid.tool.toolsbox.largeread.R;
import com.leoandroid.tool.toolsbox.largeread.activity.ToolsMainActivity;
import com.leoandroid.tool.toolsbox.pedometer.util.API23Wrapper;
import com.leoandroid.tool.toolsbox.pedometer.util.Constants;
import com.leoandroid.tool.toolsbox.pedometer.util.Logger;
import com.leoandroid.tool.toolsbox.pedometer.util.Util;

import java.util.Date;


public class SensorListener extends Service implements SensorEventListener {

    public final static int NOTIFICATION_ID = 1001;
    private final static long MICROSECONDS_IN_ONE_MINUTE = 60000000;
    private final static long SAVE_OFFSET_TIME = AlarmManager.INTERVAL_HOUR;
    private final static int SAVE_OFFSET_STEPS = 10;

    private static int steps;
    private static int lastSaveSteps;
    private static long lastSaveTime;

    private final BroadcastReceiver shutdownReceiver = new ShutdownRecevier();
    private NotificationManager mNotificationManager;

    @Override
    public void onAccuracyChanged(final Sensor sensor, int accuracy) {
        // nobody knows what happens here: step value might magically decrease
        // when this method is called...
        if (BuildConfig.DEBUG) {
            Logger.log(sensor.getName() + " accuracy changed: " + accuracy);
        }
    }

    @Override
    public void onSensorChanged(final SensorEvent event) {
        if (event.values[0] > Integer.MAX_VALUE) {
            if (BuildConfig.DEBUG) {
                Logger.log("probably not a real value: " + event.values[0]);
            }
        } else {
            steps = (int) event.values[0];
            if (BuildConfig.DEBUG) {
                Logger.log("updateIfNecessary() >>> onSensorChanged");
            }
            updateIfNecessary(false);

            if (BuildConfig.DEBUG) {
                Logger.log("onSensorChanged:: steps: " + steps);
            }
        }
    }

    /**
     * @return true, if notification was updated
     */
    public boolean updateIfNecessary(boolean isInit) {
        if (isInit || steps > lastSaveSteps + SAVE_OFFSET_STEPS ||
                (steps > 0 && System.currentTimeMillis() > lastSaveTime + SAVE_OFFSET_TIME)) {
            if (BuildConfig.DEBUG) {
                Logger.log(
                        "saving steps: steps=" + steps + " lastSave=" + lastSaveSteps +
                                " lastSaveTime=" + new Date(lastSaveTime));
            }
            Database db = Database.getInstance(this);
            if (db.getSteps(Util.getToday()) == Integer.MIN_VALUE) {
                int pauseDifference = steps - getSharedPreferences("pedometer", Context.MODE_PRIVATE)
                        .getInt("pauseCount", steps);

                db.insertNewDay(Util.getToday(), steps - pauseDifference);
                if (pauseDifference > 0) {
                    // update pauseCount for the new day
                    getSharedPreferences("pedometer", Context.MODE_PRIVATE).edit()
                            .putInt("pauseCount", steps).commit();
                }
            }
            db.saveCurrentSteps(steps);
            lastSaveSteps = steps;
            lastSaveTime = System.currentTimeMillis();

            int today_offset = db.getSteps(Util.getToday());
            if (steps == 0) {
                steps = db.getCurrentSteps(); // use saved value if we haven't anything better
            }
            db.close();
            showNotification(today_offset); // update notification
            PedometerManager.getInstance().notifyObserver(today_offset + steps);
            return true;
        } else {
            return false;
        }
    }

    public void showNotification(int today_offset) {
        if (Build.VERSION.SDK_INT >= 26) {
            startForeground(NOTIFICATION_ID, getNotification(this, today_offset));
        }
    }

    public class LocalBinder extends Binder {
        public SensorListener getService() {
            return SensorListener.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }

    @Override
    public int onStartCommand(final Intent intent, int flags, int startId) {
        if (intent == null || !intent.getBooleanExtra("noNeedShowNotify", false)) {
            createNotificationChannel(this);
            updateIfNecessary(true);
            if (BuildConfig.DEBUG) {
                Logger.log("updateIfNecessary() >>> onStartCommand");
            }
        }
        reRegisterSensor();
        registerBroadcastReceiver();

        // restart service every hour to save the current step count
        long nextUpdate = Math.min(Util.getTomorrow(),
                System.currentTimeMillis() + AlarmManager.INTERVAL_HOUR);
        if (BuildConfig.DEBUG) Logger.log("next update: " + new Date(nextUpdate).toLocaleString());
        AlarmManager am =
                (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        PendingIntent pi = PendingIntent
                .getService(getApplicationContext(), 2, new Intent(this, SensorListener.class),
                        PendingIntent.FLAG_IMMUTABLE);
        if (Build.VERSION.SDK_INT >= 23) {
            API23Wrapper.setAlarmWhileIdle(am, AlarmManager.RTC, nextUpdate, pi);
        } else {
            am.set(AlarmManager.RTC, nextUpdate, pi);
        }

        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (BuildConfig.DEBUG) {
            Logger.log("SensorListener onCreate");
        }
    }

    @Override
    public void onTaskRemoved(final Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        if (BuildConfig.DEBUG) Logger.log("sensor service task removed");
        // Restart service in 500 ms
        ((AlarmManager) getSystemService(Context.ALARM_SERVICE))
                .set(AlarmManager.RTC, System.currentTimeMillis() + 500, PendingIntent
                        .getService(this, 3, new Intent(this, SensorListener.class), 0));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (BuildConfig.DEBUG) Logger.log("SensorListener onDestroy");
        try {
            SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
            sm.unregisterListener(this);
        } catch (Exception e) {
            if (BuildConfig.DEBUG) Logger.log(e);
            e.printStackTrace();
        }
    }

    public void createNotificationChannel(Context mContext) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = mContext.getString(R.string.app_name);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
            NotificationChannel mChannel = new NotificationChannel(NOTIFICATION_ID + "", name, importance);
            mChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
            mNotificationManager.createNotificationChannel(mChannel);
        }
    }

    private static Notification getNotification(final Context context, int today_offset) {
        if (BuildConfig.DEBUG) {
            Logger.log("getNotification");
        }
        SharedPreferences prefs = context.getSharedPreferences("pedometer", Context.MODE_PRIVATE);
        int goal = prefs.getInt("goal", 10000);
        RemoteViews notificationLayout = new RemoteViews(context.getPackageName(), R.layout.layout_notification_run);
        if (steps > 0) {
            if (today_offset == Integer.MIN_VALUE) {
                today_offset = -steps;
            }
            String message = "您已经走了" + (today_offset + steps) + "步，继续加油~";
            notificationLayout.setTextViewText(R.id.tv_message, message);
        } else {
            notificationLayout.setTextViewText(R.id.tv_message, "记步中...");
        }

        Intent intent = new Intent(context, ToolsMainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(Constants.INTENT_FROM, Constants.NOTIFICATION);
        Notification
                notification = new NotificationCompat.Builder(context, NOTIFICATION_ID + "")
                .setVisibility(NotificationCompat.VISIBILITY_SECRET)
                .setSmallIcon(R.drawable.ic_notification)
                .setOnlyAlertOnce(true)
                .setCustomContentView(notificationLayout)
                .setContentIntent(PendingIntent
                        .getActivity(context, 0, intent,
                                PendingIntent.FLAG_IMMUTABLE))
                .setSilent(true)
                .build();

        return notification;
    }

    private void registerBroadcastReceiver() {
        if (BuildConfig.DEBUG) Logger.log("register broadcastreceiver");
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SHUTDOWN);
        registerReceiver(shutdownReceiver, filter);
    }

    private void reRegisterSensor() {
        if (BuildConfig.DEBUG) Logger.log("re-register sensor listener");
        SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
        try {
            sm.unregisterListener(this);
        } catch (Exception e) {
            if (BuildConfig.DEBUG) Logger.log(e);
            e.printStackTrace();
        }

        if (BuildConfig.DEBUG) {
            Logger.log("step sensors: " + sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size());
            if (sm.getSensorList(Sensor.TYPE_STEP_COUNTER).size() < 1) {
                return;
            }
            Logger.log("default: " + sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER).getName());
        }

        // enable batching with delay of max 1 min
        sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),
                SensorManager.SENSOR_DELAY_NORMAL, (int) (0.5 * MICROSECONDS_IN_ONE_MINUTE));
    }
}
